home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / SourceCode / ExecutiveTest / Executive.m < prev    next >
Text File  |  1995-06-12  |  13KB  |  439 lines

  1. /*------------------------------------------------------------------------------
  2.     Executive.m                Copyright (c) 1990, Marc A. Davidson
  3.  
  4.     SYNOPSIS
  5.             Handles executing commands on files either through system() or pipe().
  6.         Using the execute:async: method or the pipe:async: method you can execute a
  7.         command asynchronous to your main thread.
  8.             To handle asynchronous commands the object uses a DPSTimedEntry.  The
  9.         timed entry is created when the first asynchronous command is issued, and
  10.         it persists until all commands that have been issued have completed.
  11.             Synchronization between the threads and the timed entry is handled by
  12.         two queues: running and done.  When a command is first forked, the
  13.         description is placed in a command_d struct and put into the running
  14.         queue.  The timed entry is then created which constantly monitors the
  15.         done queue.  The command_d is removed from the running queue and placed
  16.         in the done queue by the forked thread when the command completes.  The
  17.         timed entry removes the entry from the done queue, decrements the counter
  18.         of currently running commands.  If it removes the last one (the counter
  19.         is decremented to 0), then it removes itself from the timed entry 
  20.         scheduler.  The timed entry is also used to send lines of output from an
  21.         asynchronous pipe:async: command.
  22.         
  23.         Some notes about the implementation:
  24.             First, the timed entry mechanism is cumbersome, but there is no other way
  25.         to synchronize the main thread (the caller's thread) and the command thread
  26.         transparently.
  27.             Second, the use of List objects in this object is a little unorthodox.
  28.         Since the List object is designed for lists of objects, using it for storing
  29.         structs is not strictly correct.  The reason for this behavior is that a
  30.         supplementary object that holds the same information as the command_d would
  31.         need to be created, resulting in two objects instead of one.  Besides, the
  32.         List object doesn't seem to care what is being manipulated (as long as you
  33.         don't use the freeObjects method!), just as long as there are pointers 
  34.         involved (NeXT will probably give me the hairy eyeball for this).
  35.  
  36.     REVISIONS
  37.     Date            Who        Modification
  38.     24-Sept-90    MAD        Created (simple synchronous routines)
  39.     5-Oct-90        MAD        Added cthread routines and queues for executing 
  40.                                 asynchronous commands.
  41.     9-Oct-90        MAD        Added asynchronous capability for pipe: methods as well.
  42.                                 After finishing, I fell asleep on the keyboarddddddddd.
  43. ------------------------------------------------------------------------------*/
  44. # import <stdlib.h>
  45. # import <ctype.h>
  46. # import <string.h>
  47. # import <libc.h>
  48. # import <errno.h>
  49. # import <appkit/Application.h>
  50. # import <appkit/Panel.h>
  51. # import <dpsclient/dpsNeXT.h>
  52. # import <objc/List.h>
  53. # import "Executive.h"
  54.  
  55. /*
  56.  * Type codes for command list entry type
  57.  */
  58. # define COMMAND_EXECUTE                0
  59. # define COMMAND_PIPE                    1
  60. # define COMMAND_PIPERESULT            2
  61.  
  62. /*
  63.  * Default period at which our main thread synchronizing timed entry executes.
  64.  */
  65. # define DEFAULT_NOTIFY_PERIOD            0.25
  66.  
  67.  
  68. /*
  69.  * this is the struct that the asynchronous routines depend upon.  They are entered
  70.  * sent into the running queue, removed and sent into the done queue, and removed
  71.  * from the done queue by the DPSTimedEntry.  Piped commands send their lines into
  72.  * the done queue for communication with the main thread.
  73.  */
  74. typedef struct command_t
  75. {    int            commandId;        /* unique identifier for command */
  76.     cthread_t    thread;            /* thread being used to execute this command */
  77.     int            type;                /* COMMAND_PIPE, COMMAND_PIPERESULT, COMMAND_EXECUTE */
  78.     id                self;                /* for communication with main thread */
  79.     char            *command;        /* command that was issued */
  80.     char            *environs;        /* environment string that was given */
  81.     int            result;            /* result of system() call (0 in case of pipe) */
  82.     char            *line;            /* the line that was read by pipe */
  83.     id                to;                /* to whom to send this line... */
  84.     SEL            action;            /* ...and method to use */
  85. } command_d;
  86.  
  87. /*
  88.  * newString allocates a new string and returns it, or returns NULL if NULL was
  89.  * passed in.
  90.  */
  91. static char *newString(CSTR s)
  92. {    char        *ns = NULL;
  93.  
  94.     if (s != NULL)
  95.     {    ns = (char *)malloc(strlen(s)+1);
  96.         strcpy(ns,s);
  97.     }
  98.     return(ns);
  99. }
  100.  
  101. /*
  102.  * methods to implement a "cheap object"
  103.  */
  104. static command_d *newCommand(int commandId,int type,CSTR command,CSTR environs,id self)
  105. {    command_d        *com;
  106.  
  107.     com = (command_d *) calloc(1,sizeof(command_d));
  108.     com->type = type;
  109.     com->commandId = commandId;
  110.     com->command = newString(command);
  111.     com->environs = newString(environs);
  112.     com->self = self;
  113.     com->result = 0;
  114.     return(com);
  115. }
  116.  
  117. /*
  118.  * new Execute command structure
  119.  */
  120. static command_d *newECommand(int commandId,CSTR command,CSTR environs,id self)
  121. {    command_d        *nc;
  122.  
  123.     nc = newCommand(commandId,COMMAND_EXECUTE,command,environs,self);
  124.     return(nc);
  125. }
  126.  
  127. /*
  128.  * new Pipe command structure
  129.  */
  130. static command_d *newPCommand(int commandId,CSTR command,CSTR environs,id self,
  131.                                         id to,SEL action)
  132. {    command_d        *nc;
  133.  
  134.     nc = newCommand(commandId,COMMAND_PIPE,command,environs,self);
  135.     nc->to = to;
  136.     nc->action = action;
  137.     return(nc);
  138. }
  139.  
  140. /*
  141.  * frees any command object
  142.  */
  143. static void freeCommand(command_d *com)
  144. {
  145.     if (com->command != NULL)
  146.         free(com->command);
  147.     if (com->environs != NULL)
  148.         free(com->environs);
  149.     if ((com->type == COMMAND_PIPERESULT) && (com->line != NULL))
  150.         free(com->line);                        /* free the allocated line */
  151.     free(com);
  152. }
  153.  
  154. static void makeCmdLine(char *cmdLine,CSTR command,CSTR environs)
  155. {
  156.     if (environs != NULL)
  157.         sprintf(cmdLine,"%s;%s",environs,command);
  158.     else
  159.         sprintf(cmdLine,"%s",command);
  160. }
  161.  
  162. static int doExecute(CSTR command,CSTR environs)
  163. {    char        cmdNull[MAXPATHLEN];
  164.     
  165.     makeCmdLine(cmdNull,command,environs);
  166.     strcat(cmdNull," >/dev/null 2>/dev/null");
  167.     return(system(cmdNull));
  168. }
  169.  
  170. /*
  171.  * doPipe works either synchronously or asynchronously.
  172.  * Note that the line allocated in each asynchronous iteration is deallocated
  173.  * when the to object has been notified via the DPSTimedEntry.
  174.  */
  175. static void doPipe(CSTR command,CSTR environs,BOOL async,id self,int commandId,
  176.                         id to,SEL aSelector)
  177. {    FILE             *fd;
  178.     char            theLine[MAXPATHLEN],
  179.                     cmdLine[MAXPATHLEN],
  180.                     *cp;
  181.     extern int    pclose(FILE *fd);
  182.     
  183.     makeCmdLine(cmdLine,command,environs);
  184.     if ((fd = popen(cmdLine,"r")) != NULL)
  185.     {    while (fgets(theLine,MAXPATHLEN,fd) != NULL)
  186.         {    if (async)
  187.             {    command_d        *nc;
  188.             
  189.                 nc = newPCommand(commandId,NULL,NULL,self,to,aSelector);
  190.                 nc->type = COMMAND_PIPERESULT;
  191.                 nc->line = newString(theLine);
  192.                 mutex_lock(((Executive *)self)->doneLock);
  193.                 [((Executive *)self)->done insertObject:(id)nc at:0];
  194.                 mutex_unlock(((Executive *)self)->doneLock);
  195.                 cthread_yield();
  196.             }
  197.             else
  198.                 [to perform:aSelector with:(id)theLine];
  199.             
  200.         }
  201.         pclose(fd);
  202.     }
  203. }
  204.  
  205. @implementation Executive
  206.  
  207. + new
  208. {
  209.     self = [super new];
  210.     curCmdId = 0;
  211.     numExecuting = 0;
  212.     period = DEFAULT_NOTIFY_PERIOD;
  213.     runningLock = mutex_alloc();
  214.     doneLock = mutex_alloc();
  215.     running = [List new];
  216.     done = [List new];
  217.     return(self);
  218. }
  219.  
  220. + newPeriod:(double)p
  221. {
  222.     self = [Executive new];
  223.     period = p;
  224.     return(self);
  225. }
  226.  
  227. - free
  228. {
  229.     mutex_free(runningLock);
  230.     mutex_free(doneLock);
  231.     [running free];
  232.     [done free];
  233.     return([super free]);
  234. }
  235.  
  236. /*---------------------------------< OUTLET METHODS >--------------------------------*/
  237. - target                                { return(target); }
  238. - setTarget:anObject                { target = anObject; return(self); }
  239. - (SEL) action                        { return(action); }
  240. - setAction:(SEL)aSelector        { action = aSelector; return(self); }
  241. - setPeriod:(double)p            { period = p; return(self); }
  242. - (double)period                    { return(period); }
  243.  
  244. /*-------------------------------< OVERRIDDEN METHODS >------------------------------*/
  245.  
  246. /*----------------------------------< OTHER METHODS >--------------------------------*/
  247. /*
  248.  * doNotify is called by the timed entry glue function to send messages to the main
  249.  * thread in an synchronous manner.  For command types it decrements the executing
  250.  * counter and removes the timed entry if all commands have completed.
  251.  * Notification of a done command is sent to the target when either a pipe command
  252.  * or execute command is completed.  Pipe results are sent to the given object and
  253.  * selector.
  254.  */
  255. - doNotify:(DPSTimedEntry)entry
  256. {    command_d        *com;
  257.  
  258.     if ([done count] != 0)
  259.     {    mutex_lock(doneLock);
  260.         com = (command_d *)[done removeLastObject];
  261.         mutex_unlock(doneLock);
  262.         switch(com->type)
  263.         {    case COMMAND_PIPE:
  264.             case COMMAND_EXECUTE:
  265.                 [target perform:action with:(id)com->commandId with:(id)com->result];
  266.                 if (--numExecuting == 0)
  267.                     DPSRemoveTimedEntry(entry);
  268.                 break;
  269.             case COMMAND_PIPERESULT:
  270.                 [com->to perform:com->action with:(id)com->commandId with:(id)com->line];
  271.                 break;
  272.             default:
  273.                 fprintf(stderr,"Executive: Retrieved unexpected command type:"
  274.                                     " %d\n",com->type);
  275.                 [NXApp terminate:self];
  276.                 break;
  277.         }
  278.         freeCommand(com);
  279.     }
  280.     return(self);
  281. }
  282.  
  283. /*
  284.  * commandDone performs the appropriate action when a command is done:  removes it
  285.  * from the running queue and places it in the done queue for removal by the
  286.  * DPSTimedEntry.  Since a pipe may be in progress and there may be many lines
  287.  * queued for sending, this will insert the program at the very beginning of
  288.  * the done queue, thus implementing a priority queue.
  289.  */
  290. - commandDone:(command_d *)com
  291. {    
  292.     mutex_lock(runningLock);
  293.     mutex_lock(doneLock);
  294.     [running removeObject:(id)com];
  295.     if (com->type == COMMAND_EXECUTE)
  296.         [done addObject:(id)com];
  297.     else
  298.         [done insertObject:(id)com at:0];
  299.     mutex_unlock(doneLock);
  300.     mutex_unlock(runningLock);
  301.     return(self);
  302. }
  303.  
  304. - (int)doAsyncExecute:(command_d *)com
  305. {    int        err;
  306.  
  307.     err = doExecute(com->command,com->environs);
  308.     [self commandDone:com];
  309.     return(err);
  310. }
  311.  
  312. /*
  313.  * this is a wrapper for the thread that runs the asynchronous command, then
  314.  * exits.
  315.  */
  316. void asyncExecute(command_d *com)
  317. {
  318.     com->result = [com->self doAsyncExecute:com];
  319. }
  320.  
  321. - doAsyncPipe:(command_d *)com
  322. {
  323.     doPipe(com->command,com->environs,YES,self,com->commandId,com->to,com->action);
  324.     [self commandDone:com];
  325.     return(self);
  326. }
  327.  
  328. /*
  329.  * this is a wrapper for the thread that runs the asynchronous pipe command, then
  330.  * exits.
  331.  */
  332. void asyncPipe(command_d *com)
  333. {
  334.     [com->self doAsyncPipe:com];
  335. }
  336.  
  337. /*
  338.  * this is a timed entry that will notify the target of the Executive when the
  339.  * asynchronous command identified by a unique number has completed.
  340.  */
  341. static void notify(DPSTimedEntry entry, double now,id self)
  342. {
  343.     [self doNotify:entry];
  344. }
  345.  
  346. /*
  347.  * addCommandToRunning adds the given command structure to the running queue,
  348.  * performing appropriate mutex_lock's and incrementing the executing count
  349.  * to reflect the add.
  350.  */
  351. - addCommandToRunning:(command_d *)com
  352. {
  353.     mutex_lock(runningLock);
  354.     [running addObject:(id)com];
  355.     mutex_unlock(runningLock);
  356.     if (++numExecuting == 1)
  357.         DPSAddTimedEntry(period,(DPSTimedEntryProc) notify,
  358.                                 (void *)self,NX_BASETHRESHOLD);
  359.     return(self);
  360. }
  361.  
  362. - (int)execute:(CSTR)command
  363. {
  364.     return([self execute:command environs:NULL async:NO]);
  365. }
  366.  
  367. - (int) execute:(CSTR)command async:(BOOL)async
  368. {
  369.     return([self execute:command environs:NULL async:async]);
  370. }
  371.  
  372. - (int) execute:(CSTR)command environs:(CSTR)environs async:(BOOL)async 
  373. {    command_d        *nc;
  374.  
  375.     if (!async)
  376.         return(doExecute(command,environs));
  377.     nc = newECommand(++curCmdId,command,environs,self);
  378.     cthread_detach(nc->thread = cthread_fork(asyncExecute,nc));
  379.     [self addCommandToRunning:nc];
  380.     return(nc->commandId);
  381. }
  382.  
  383. - (int)pipe:(CSTR)command to:anObject :(SEL)aSelector async:(BOOL)async
  384. {
  385.     return([self pipe:command environs:NULL to:anObject :aSelector async:async]);
  386. }
  387.  
  388. - (int)pipe:(CSTR)command environs:(CSTR)environs to:anObject :(SEL)aSelector
  389.         async:(BOOL)async
  390. {    command_d        *nc;
  391.     
  392.     if (!async)
  393.     {    doPipe(command,environs,async,nil,0,anObject,aSelector);
  394.         return(0);
  395.     }
  396.     nc = newPCommand(++curCmdId,command,environs,self,anObject,aSelector);
  397.     [self addCommandToRunning:nc];
  398.     cthread_detach(nc->thread = cthread_fork(asyncPipe,nc));
  399.     return(nc->commandId);
  400. }
  401.  
  402. - showError:(int)err
  403. {
  404.     return([self showError:err while:"executing a command" on:"on a file"
  405.                 using:"File"]);
  406. }
  407.  
  408. - showError:(int)err while:(CSTR)doingWhat
  409. {
  410.     return([self showError:err while:doingWhat on:"a file" using:"File"]);
  411. }
  412.  
  413. - showError:(int)err while:(CSTR)doingWhat on:(CSTR)fname
  414. {
  415.     return([self showError:err while:doingWhat on:fname using:"File"]);
  416. }
  417.  
  418.  
  419. - showError:(int)err while:(CSTR)doingWhat on:(CSTR)fname using:(CSTR)prog
  420. {    char    err_str1[MAXPATHLEN],
  421.             err_str2[MAXPATHLEN];
  422.  
  423.     sprintf(err_str1,"%s error",prog);
  424.     sprintf(err_str2,"Error while %s %s (error code %d)",doingWhat,fname,err);
  425.     NXRunAlertPanel(err_str1,err_str2,"Ok",NULL,NULL);
  426.     return(self);
  427. }
  428.  
  429. - showFError:(CSTR)fname
  430. {    
  431.     NXRunAlertPanel("File error","Can't access %s (%s).","Ok",
  432.                             NULL,NULL,fname,sys_errlist[errno]);
  433.     return(self);
  434. }
  435.  
  436. /*--------------------------------< DELEGATE METHODS >-------------------------------*/
  437.  
  438. @end
  439.